How to not be evil [Update]

Wer kennt sie nicht, die beliebte Funktion eval()? Chrome zum Beispiel 😀

Das heißt auch, dass die bisher im Edgemonkey verwendete Kombination von eval/uneval zur (de)serialisierung von Einstellung und Speicherung dieser so in Chrome nie funktionieren wird (selbst wenn später einmal eine GM_setValue-Alternative zur Verfügung steht – stay tuned). Es muss also jedes Vorkommen davon durch die eigentlich viel schöneren und moderneren JSON.parse und JSON.stringify ersetzt werden. Was aber nicht so einfach ist, da die Migration bisheriger Daten natürlich funktionieren muss.
Was gar nicht so einfach ist – uneval erzeugt kein JSON, sondern nur etwas was so ähnlich aussieht!

Man muss also etwas tun, um die Daten in valides JSON zu konvertieren. Was nicht einfach geht, ohne uneval. Bleibt also nur, den String als solchen zu bearbeiten. And here’s how:

JSON.convertFromUneval = function(u) {
  function protect(s) {
    return s.replace(/./g, function(m) {
      return '#'+m.charCodeAt(0).toString(16);
    })
  }
  function unprotect(s) {
    return s.replace(/#([a-f0-9]+)/g, function(m,c) {
      return String.fromCharCode(parseInt(c,16))
    })
  }
 
  return u.
    // remove outer braces
    match(/^((.*))$/)[1].
    // protect strings
    replace(/(?::"")|(?::"(.*?)([^\]"))/g,function(m,g1,g2) {
      return ':"'+(isUndef(g2)?'':protect(g1+g2.charAt(0)))+'"';
    }).
    // prepare unquoted param names using single ticks
    replace(/([{,])s*([^:'{,]+?):/g,function(g,g1,g2) {
      return g1+"'"+g2+"':";
    }).
    // remove undefined, JSON has no notation of that data type
    replace(/([,{])s?'(?:[^']|(?:(?:\\)*\'))*':(void 0)([,}])?/g,function(g,g1,g2) {
      return ((g1!=='{')?g1='':g1) + ((g1 && g2!=='}')?'':g2);
    }).
    // turn single into double tics
    replace(/'(.+?)':/g,function(g,g1) {
      return '"'+g1.replace(/\'/g,"'").replace(/"/g,'\"')+'":';
    }).
    // unprotect strings (unwind x sequences: JSON as a Unicode standard doesn't have ASCII escapes)
    // and do that w/o lookbehinds, which would be waaaay to easy
    replace(/:"((#[a-f0-9]+)+)"/g,function(m,t) {
      return ':"'+unprotect(t).replace(/(?:(?:\\)*\)*(\x([0-9A-F]{2}))/g, function(m,g1,g2) {
        return (m.length%2)?m:m.replace(g1,String.fromCharCode(parseInt(g2,16)));
      })+'"';
    })
}
 
JSON.parseUneval = function(u) {
  return JSON.parse(JSON.convertFromUneval(u));
}

Ist diesmal sogar kommentiert, da brauch ich gar nichts weiter schreiben. Schön! Man könnte das ganze zwar sogar noch weiter einkürzen, aber wer will das schon – soll ja auch noch lesbar sein.
Was jetzt noch zu tun bliebe, wäre JSON (oder uneval’d code) zweifelsfrei erkennen – aber hier werde ich wohl trial&error nutzen: „Wenn es sich nicht als JSON parsen lässt, wirds wohl was anderes sein“. Soll sich doch Spidermonkey damit rumärgern! 😀

Dieser Schnipsel steht wie der Rest des Affen unter CC-BY-NC-SA. Ergänzungen und Fixes willkommen, sollte z.B. mein Test-Objekt noch seltsame Sachen nicht enthalten!

Update: Kommentator BenBE hatte doch richtig vermutet: da war noch einiges holprig drin. Ein 316kByte-Datengewusel ist halt doch ein besserer Testcase als etwas konstruiertes 😉
Der String-protect-Teil ist nach wie vor nicht wirklich korrekt, wie jeder einfache Testcase zeigt. Bis zum Beweis des Gegenteils hoffe ich aber, dass es für den Affen reicht.

2 thoughts on “How to not be evil [Update]

  1. Dein Unprotect ist greedy, obwohl nur jeweils 2 Zeichen gematcht werden sollten. Könnte zu Problemen führen und laut Murphy wird es das auch ^^

    Auch sonst sieht mir deine Protect/Unprotect-Struktur noch ziemlich wacklig aus … *EXPLAIN*

  2. Stimmt, statt dem + hätte an da auch ein {2,4} schreiben können. Geht aber darum, dass es in dieser Form auch Unicode-Safe ist. Und ich *weiß*, was für Daten kommen.
    Und wegen wacklig: ist es nicht, das ist Firebug-Console-Test-Driven-Development 😛

    Was man dazu wissen muss ist allerdings: dieser Code ist stark auf das was Spidermonkey’s uneval erzeugt zugeschnitten. Zum Beispiel treten Double-Quotes original nur an Strings auf, daher kann man hier einige Annahmen treffen die nicht direkt ersichtlich sind.

Comments are closed.